/*
 * @Author: hongbin
 * @Date: 2022-10-23 20:27:29
 * @LastEditors: hongbin
 * @LastEditTime: 2022-12-21 20:20:28
 * @Description: 加载处理辽东地图
 */
import { GLTF } from "three/examples/jsm/loaders/GLTFLoader";
import THREE from "../";
import { get } from "../../api";
import { LoadProcess } from "../../helper";
import { nearCheck } from "../helper";
import { loadGltf } from "../helper/loaderHelper";
import { IGetModel } from "../types";
import { RandomColor } from "../utils";

/**
 * 匹配不需要自定义材质的mesh命 CM - Custom Material(自定义材质)
 */
const _windowReg = new RegExp(/^(窗户|CM)/);
/**
 * 除了地面接收阴影的物体
 */
const receiveShadowObjs = ["7号楼小草坪"];
/**
 * 鉴别采用原模型材质的物体的方法
 */
const checkIsChangeMaterialObjs = [(name: string) => name[0] === "灯", (name: string) => _windowReg.test(name)];

/**
 * 物体处理 阴影、材质
 */
const handleMesh = (mesh: Mesh) => {
    //是否不需要计算生成材质
    let noChangeMaterial = checkIsChangeMaterialObjs.some((check) => check(mesh.name));
    if (noChangeMaterial) return;
    //材质有颜色
    if (mesh.material) {
        if (Array.isArray(mesh.material)) {
            noChangeMaterial = true;
        } else {
            //@ts-ignore
            const color = mesh.material.color as THREE.Color;
            noChangeMaterial = color.r + color.g + color.b != 3;
        }
    }
    if (noChangeMaterial) return;

    // console.log(mesh.geometry.attributes);
    // 实验得知 不需要uv也可应用material 但没有normal不行
    // 导出时为减小体积没有包含uv - 手动填充
    // if (!mesh.geometry.attributes.uv) {
    //     const { count } = mesh.geometry.attributes.position;
    //     const uv = new Float32Array(count * 2);
    //     for (let i = 0; i < count; i++) {
    //         const i2 = i * 2;
    //         uv[i2] = 0;
    //         uv[i2 + 1] = 1;
    //     }
    //     mesh.geometry.attributes.uv = new THREE.BufferAttribute(uv, 2);
    // }

    mesh.material = new THREE.MeshLambertMaterial({
        color: new RandomColor(),
        //默认平滑着色
        // flatShading: true,
        side: 2,
    });
    mesh.castShadow = true;
    mesh.receiveShadow = false;

    //处理需要接收阴影的物体
    if (receiveShadowObjs.includes(mesh.name)) {
        mesh.receiveShadow = true;
    }
};

/**
 * 聚光灯设置
 * 受灯光阴影限制 不能同时计算大量阴影 three似乎一并会计算看不到的场景
 * 需要默认关闭所以需要阴影的聚光灯
 * 根据位置开启附近的聚光灯阴影计算
 */
const handleSpotLight = (light: THREE.SpotLight) => {
    // light.castShadow = false;
    // light.shadow.mapSize.width = 2048;
    // light.shadow.mapSize.height = 2048;
    // light.shadow.camera.near = 0.5;
    // light.shadow.camera.far = 500;
    // light.shadow.focus = 1;
    // light.shadow.bias = -0.00005;
};

/**
 * 地面处理 地面接收阴影但不产生阴影
 */
const handleFloor = (floor: Mesh | undefined) => {
    if (!floor) throw new Error("没有找到floor!");
    floor.castShadow = false;
    floor.receiveShadow = true;
    nearCheck.mustCheckObjects.push(floor);
};

/**
 * 辽东地图模型
 * 按优先级加载
 */
export async function loadLDMap(objects: Object3D, withVolume: Object3D[], complete?: (scene?: THREE.Scene) => void) {
    const { data } = await get<{ data: Array<IGetModel> }>("/models/list");
    console.log(data);
    if (!data) return new Error("获取模型列表失败");

    // return loadAllMap(objects, withVolume, complete);

    // 根据order排序 分优先级加载 {0:[{...}],1:[{...},{...}]}
    // const sort = data.sort((x,y) => x.order - y.order);
    const orderModels = data.reduce((prev, curr) => {
        if (prev[curr.order]) prev[curr.order].push(curr);
        else prev[curr.order] = [curr];
        return prev;
    }, {} as Record<number, IGetModel[]>);

    const handle = (gltf: GLTF) => {
        gltf.scene.traverse((obj: any) => {
            const { type } = obj;
            if (type === "Mesh") {
                handleMesh(obj);
            }
            //灯光会严重影响性能 移除灯光
            else if (type === "SpotLight") {
                // console.log(obj);
                // handleSpotLight(obj);
            }
        });

        const floor = gltf.scene.getObjectByName("floor") as Mesh;
        floor && handleFloor(floor);

        // 向scene中添加 scene three的计算工作非常耗时 总时长比一次全添加还长很多
        // objects.add(gltf.scene);
        // withVolume.push(gltf.scene);
    };

    console.time();
    /**
     * @description: 分片加载
     */
    const partLoad = (part: number, orderRecord: Record<number, IGetModel[]>, complete?: VoidFunction) => {
        const ms = orderRecord[part];
        if (!ms) {
            console.timeEnd();
            return complete && complete();
        }

        const scenes: THREE.Group[] = [];

        Promise.all(
            ms.map((model) =>
                loadGltf(`api/glb?name=${model.fileName}`).then((gltf) => {
                    scenes.push(gltf.scene);
                    handle(gltf);
                })
            )
        ).finally(() => {
            console.log(`当前优先级：${part}`);
            // objects.add(...scenes);
            const g = new THREE.Object3D();
            if (part === 0) {
                LoadProcess.canShow();
            }
            g.add(...scenes);
            g.scale.set(30, 30, 30);
            objects.add(g);

            scenes.forEach((scene) => nearCheck.calculationMeshWorldPosition(scene));

            window.render();
            // 放到scene后才有确切的位置
            //更新场景
            // complete && complete();
            //加载下一优先级模型
            partLoad(part + 1, orderRecord, complete);
        });
    };

    partLoad(0, orderModels, () => {
        complete && complete();
    });
}

/**
 * 逐帧添加物体 一个个添加
 */
class FrameAddObjectClass {
    objList = [] as Object3D[];

    constructor() {}

    addWaitObject() {}

    /**
     * script 逐帧调用 添加物体
     */
    update() {}
}

const frameAddObject = new FrameAddObjectClass();

const loadAllMap = (objects: Object3D, withVolume: Object3D[], complete?: (scene?: THREE.Scene) => void) => {
    console.time();
    loadGltf("api/glb?name=ld-map-memo.gltf")
        .then((gltf) => {
            gltf.scene.traverse((obj: any) => {
                const { type } = obj;
                if (type === "Mesh") {
                    handleMesh(obj);
                } else if (type === "SpotLight") {
                    handleSpotLight(obj);
                }
            });

            handleFloor(gltf.scene.getObjectByName("floor") as Mesh);
            nearCheck.calculationAllMeshWorldPosition(gltf.scene);
            gltf.scene.scale.set(30, 30, 30);
            gltf.scene.name = "LD-MAP";
            objects.add(gltf.scene);
            withVolume.push(gltf.scene);
            window.render();
            complete && complete(gltf.scene as unknown as THREE.Scene);
            console.timeEnd();
        })
        .catch((err) => {
            console.log("err", err);
        });
};
